// License: Attribution 4.0 International (CC BY 4.0)
/*--- __ECO__ __PLAYMAKER__ __ACTION__ ---*/

using System.Threading;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
	[ActionCategory(ActionCategory.Material)]
	[Tooltip("Resize the texture with another method - Only works on ARGB32, RGB24 and Alpha8 textures that are marked readable")]
    [HelpUrl("http://hutonggames.com/playmakerforum/index.php?topic=12349.0")]
	public class ResizeTexture2 : FsmStateAction
	{
		[ActionSection("Input")]
		public FsmTexture texture;

		[ActionSection("Setup")]
		public FsmInt setWidth;
		public FsmInt setHeight;


		[ActionSection("Output")]
		public FsmTexture newTexture;

		[ActionSection("Option")]
		public FilterMode filterMode;

		public enum FilterMode{
			Point,
			Biliner,
		}


		private Texture2D texItem;

		public override void Reset()
		{
			newTexture = null;
			texture = null;
			setWidth = 0;
			setHeight = 0;
		}

		public override void OnEnter()
		{

			if (texture.IsNone || texture.Value == null)
			{
				Debug.LogWarning("<b>[ResizeTexture]</b><color=#FF9900ff> Empty Input - Please review!</color>", this.Owner);
				Finish ();
			}

			if (newTexture.IsNone || newTexture.Value == null)
			{
				Debug.LogWarning("<b>[ResizeTexture]</b><color=#FF9900ff> Empty Output - Please review!</color>", this.Owner);
				Finish ();
			}

			texItem = texture.Value as Texture2D;

			switch (filterMode)
			{
			case FilterMode.Point:
				TextureScale.Point (texItem, setWidth.Value, setHeight.Value);
				break;
			case FilterMode.Biliner:
				TextureScale.Bilinear (texItem, setWidth.Value, setHeight.Value);
				break;
			}


			newTexture.Value = texItem;
			texItem = null;

			Finish();
		}

		public class TextureScale
		{
			public class ThreadData
			{
				public int start;
				public int end;
				public ThreadData (int s, int e) {
					start = s;
					end = e;
				}
			}
			
			private static Color[] texColors;
			private static Color[] newColors;
			private static int w;
			private static float ratioX;
			private static float ratioY;
			private static int w2;
			private static int finishCount;
			private static Mutex mutex;
			
			public static void Point (Texture2D tex, int newWidth, int newHeight)
			{
				ThreadedScale (tex, newWidth, newHeight, false);
				return;
			}
			
			public static void Bilinear (Texture2D tex, int newWidth, int newHeight)
			{
				ThreadedScale (tex, newWidth, newHeight, true);
				return;
			}
			
			private static void ThreadedScale (Texture2D tex, int newWidth, int newHeight, bool useBilinear)
			{
				texColors = tex.GetPixels();
				newColors = new Color[newWidth * newHeight];
				if (useBilinear)
				{
					ratioX = 1.0f / ((float)newWidth / (tex.width-1));
					ratioY = 1.0f / ((float)newHeight / (tex.height-1));
				}
				else {
					ratioX = ((float)tex.width) / newWidth;
					ratioY = ((float)tex.height) / newHeight;
				}
				w = tex.width;
				w2 = newWidth;
				var cores = Mathf.Min(SystemInfo.processorCount, newHeight);
				var slice = newHeight/cores;
				
				finishCount = 0;
				if (mutex == null) {
					mutex = new Mutex(false);
				}
				if (cores > 1)
				{
					int i = 0;
					ThreadData threadData;
					for (i = 0; i < cores-1; i++) {
						threadData = new ThreadData(slice * i, slice * (i + 1));
						ParameterizedThreadStart ts = useBilinear ? new ParameterizedThreadStart(BilinearScale) : new ParameterizedThreadStart(PointScale);
						Thread thread = new Thread(ts);
						thread.Start(threadData);
					}
					threadData = new ThreadData(slice*i, newHeight);
					if (useBilinear)
					{
						BilinearScale(threadData);
					}
					else
					{
						PointScale(threadData);
					}
					while (finishCount < cores)
					{
						Thread.Sleep(1);
					}
				}
				else
				{
					ThreadData threadData = new ThreadData(0, newHeight);
					if (useBilinear)
					{
						BilinearScale(threadData);
					}
					else
					{
						PointScale(threadData);
					}
				}
				
				tex.Resize(newWidth, newHeight);
				tex.SetPixels(newColors);
				tex.Apply();

				return;
			}
			
			public static void BilinearScale (System.Object obj)
			{
				ThreadData threadData = (ThreadData) obj;
				for (var y = threadData.start; y < threadData.end; y++)
				{
					int yFloor = (int)Mathf.Floor(y * ratioY);
					var y1 = yFloor * w;
					var y2 = (yFloor+1) * w;
					var yw = y * w2;
					
					for (var x = 0; x < w2; x++) {
						int xFloor = (int)Mathf.Floor(x * ratioX);
						var xLerp = x * ratioX-xFloor;
						newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor+1], xLerp),
						                                       ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor+1], xLerp),
						                                       y*ratioY-yFloor);
					}
				}
				
				mutex.WaitOne();
				finishCount++;
				mutex.ReleaseMutex();
			}
			
			public static void PointScale (System.Object obj)
			{
				ThreadData threadData = (ThreadData) obj;
				for (var y = threadData.start; y < threadData.end; y++)
				{
					var thisY = (int)(ratioY * y) * w;
					var yw = y * w2;
					for (var x = 0; x < w2; x++) {
						newColors[yw + x] = texColors[(int)(thisY + ratioX*x)];
					}
				}
				
				mutex.WaitOne();
				finishCount++;
				mutex.ReleaseMutex();
			}
			
			private static Color ColorLerpUnclamped (Color c1, Color c2, float value)
			{
				return new Color (c1.r + (c2.r - c1.r)*value, 
				                  c1.g + (c2.g - c1.g)*value, 
				                  c1.b + (c2.b - c1.b)*value, 
				                  c1.a + (c2.a - c1.a)*value);
			}
		}

	}
}
